# -*- coding:utf-8 -*-

import os
import time
import traceback

import numpy
from threading import Thread
from x6502 import X6502

DEBUG = 1
DISPLAY_WIDTH = 160
DISPLAY_HEIGHT = 96
DISPLAY_SCALE = 4
DISPLAY_FILL = 0x000000
DISPLAY_EMPTY = 0xFFFFFF


class BA4988(Thread):
    def __init__(self):
        super().__init__()
        self.isRunning = False
        self.display = numpy.zeros((DISPLAY_WIDTH*DISPLAY_SCALE, DISPLAY_HEIGHT*DISPLAY_SCALE))
        self.bank_mapping = [0x0000]*0x10
        # self.bank_mapping = [0x0000,
        #                      0x0001, 0x0002, 0x0003, 0x0004,
        #                      0x0E9C, 0x0E9D, 0x0E9E, 0x0E9F,
        #                      # 0x0EA0, 0x0EA1, 0x0EA2, 0x0EA3,
        #                      0x0E80, 0x0E81, 0x0E82, 0x0E83,
        #                      0x0E88, 0x0E89, 0x0E8A]
        font_path = os.path.join('data', 'FONT.ROM')
        f = open(font_path, 'rb')
        self.font = f.read()
        f.close()
        self.memory = bytearray(0x1001)
        image_path = os.path.join('data', 'BA4988.DAT')
        f = open(image_path, 'rb')
        content = f.read()
        f.close()
        # 复制boot部分
        self.memory[0x300:0x400] = bytearray(content[0x300:0x400])
        self.flash = bytearray(0x10000000)  # 256MB
        self.banks = [0x0000, 0xFFFC,
            0x0200, 0x0204, 0x0208, 0x020C,
            0x03F0, 0x03F4, 0x03F8, 0x03FC,
            0x0E80, 0x0E84, 0x0E88, 0x0E8C,
            0x0E90, 0x0E94, 0x0E98, 0x0E9C,
            0x0EA0, 0x0EA4, 0x0EA8, 0x0EAC,
            0x0EB0, 0x0EB4, 0x0EB8, 0x0EBC,
            0x0EC0, 0x0EC4, 0x0EC8, 0x0ECC,
            0x0ED0, 0x0ED4, 0x0ED8, 0x0EDC,
            0x0EE0, 0x0EE4, 0x0EE8, 0x0EEC,
            0x0EF0, 0x0EF4, 0x0EF8, 0x0EFC,
            0x0F00, 0x0F04, 0x0F08, 0x0F0C,
            0x0F10, 0x0F14, 0x0F18, 0x0F1C,
            0x0F20, 0x0F24, 0x0F28, 0x0F2C,
            0x0F40, 0x0F44, 0x0F48, 0x0F4C,
            0x0F80, 0x0F84, 0x0F88, 0x0F8C]

        # image_path = 'test.bin'
        # f = open(image_path, 'rb')
        # content = f.read()
        # f.close()
        # self.memory[0x300:0x400] = content[0xFFF00:0x100000]
        for bank in self.banks:
            # if bank == 0x0001:
            #     image_path = os.path.join('data', 'BA4988.DAT')
            #     f = open(image_path, 'rb')
            #     content = f.read()
            #     f.close()
            #     self.flash[0x1000:0x5000] = content[0x1000:0x5000]
            #     continue
            if not os.path.isfile(os.path.join('data', '{:04X}.DAT'.format(bank))):
                print('not found {:04X}.DAT'.format(bank))
                continue
            f = open(os.path.join('data', '{:04X}.DAT'.format(bank)), 'rb')
            content = f.read()
            f.close()
            self.flash[bank<<12:(bank<<12)+len(content)] = content

        self.x6502 = X6502(self.getPhysicalAddress, self.RdMem, self.WrMem)
        self.x6502.PC = 0x0350  # system entry point
        # self.x6502.PC = 0x6FA2  # SysStart
        # self.x6502.PC = 0xE907  # void DataBankSwitch(U8 logicStartBank,U8 bankNumber,U16 physicalStartBank);
        # self.x6502.PC = 0xE90A  # void GetDataBankNumber(U8 logicStartBank,U16* physicalBankNumber);
        # self.x6502.PC = 0xD2F6  # __banked_function_call
        # self.x6502.PC = 0xE8F8  # _get_current_bank_number
        # self.x6502.PC = 0xE8FB  # _switch_bank_number
        # self.x6502.PC = 0xE8FE  # _restore_bank_number
        # self.x6502.PC = 0xE90D  # SysWriteCom
        # self.x6502.PC = 0xE910  # SysReadCom

        # self.x6502.PC = 0x5046  # application enter point
        # self.x6502.PC = 0xf8cf

    def setIRQ(self, addr):
        self.x6502.setIRQ(addr)

    def getPhysicalAddress(self, addr):
        return (self.bank_mapping[addr>>12]<<12)+(addr&0xFFF)

    def RdMem(self, addr, direct=False):
        # if addr in (0x00, 0x01, 0x02, 0x03):  # _DATA1 _DATA2 _DATA3 _DATA4
        #     font_addr = self.memory[0x208+addr*3]+(self.memory[0x208+addr*3+1]<<8)+(self.memory[0x208+addr*3+2]<<16)-0x800000
        #     if not direct:
        #         self.memory[0x208+addr*3] = (self.memory[0x208+addr*3]+1)&0xFF
        #         if self.memory[0x208+addr*3] == 0:
        #             self.memory[0x208+addr*3+1] = (self.memory[0x208+addr*3+1]+1)&0xFF
        #         if self.memory[0x208+addr*3+1] == 0:
        #             self.memory[0x208+addr*3+2] = (self.memory[0x208+addr*3+2]+1)&0xFF
        #         self.memory[addr] = self.font[font_addr]
        #     return self.font[font_addr]
        if addr < 0x300:
            return self.memory[addr]
        elif addr < 0x400:
            return self.memory[addr]
        elif addr < 0x1000:
            return self.memory[addr]
        elif addr < 0x10000:
            if 0x5000 <= addr < 0xD000 and self.bank_mapping[addr>>12]&0xFFFC not in self.banks:
                raise RuntimeError('[0x{:04X}]mapping error!'.format(addr>>12))
            flash_addr = (self.bank_mapping[addr>>12]<<12)+(addr&0xFFF)
            return self.flash[flash_addr]
        raise RuntimeError('address: 0x{:04X} is exceed 0xFFFF'.format(addr))

    def WrMem(self, addr, data):
        data &= 0xFF
        # if addr in (0x00, 0x01, 0x02, 0x03):  # _DATA1 _DATA2 _DATA3 _DATA4
        #     font_addr = self.memory[0x208+addr*3]+(self.memory[0x208+addr*3+1]<<8)+(self.memory[0x208+addr*3+2]<<16)-0x800000
        #     self.memory[0x208+addr*3] = (self.memory[0x208+addr*3]+1)&0xFF
        #     if self.memory[0x208+addr*3] == 0:
        #         self.memory[0x208+addr*3+1] = (self.memory[0x208+addr*3+1]+1)&0xFF
        #     if self.memory[0x208+addr*3+1] == 0:
        #         self.memory[0x208+addr*3+2] = (self.memory[0x208+addr*3+2]+1)&0xFF
        #     self.font[font_addr] = data
        if addr < 0x300:
            self.memory[addr] = data
            if addr == 0x08:
                print('SysWriteCom:', hex(data))
                self.memory[0x09] |= 0x20
            elif addr == 0x0C:  # _BK_SEL
                self.memory[0x0D] = self.bank_mapping[data]&0xFF
                self.memory[0x0E] = self.bank_mapping[data]>>8
            elif addr == 0x0D:  # _BK_ADRL
                self.bank_mapping[self.memory[0x0C]] = (self.bank_mapping[self.memory[0x0C]]&0xFF00) + data
            elif addr == 0x0E:  # _BK_ADRH
                self.bank_mapping[self.memory[0x0C]] = (self.bank_mapping[self.memory[0x0C]]&0x00FF) + (data<<8)
            elif addr == 0x200:  # _SYSCON
                while data&0x08 and not self.x6502.IRQ_PC:
                    time.sleep(0.01)
        elif addr < 0x400:
            pass
        elif addr <= 0x1000:
            # print(f'W {addr:04X}: {data:02X}')
            self.memory[addr] = data
            if addr & 0x1F == 0x13:
                x = 0
                if addr < 0x0C20:
                    y = (0x0C13-addr)//0x20
                elif addr < 0x0FE0:
                    y = 66+(addr-0x0C33)//0x20
                else:
                    y = 65
            elif addr == 0x1000:
                y=65
                x=1
            else:
                if addr < 0x0C40:
                    y = (0x0C3F-addr)//0x20
                    x = (addr%0x20)+1
                else:
                    y = 66+(addr-0x0C40)//0x20
                    x = (addr%0x20)+1
            for i in range(8):
                if data & (0x80 >> i):
                    self.display[(x*8+i)*DISPLAY_SCALE:(x*8+i+1)*DISPLAY_SCALE,
                        y*DISPLAY_SCALE:(y+1)*DISPLAY_SCALE] = DISPLAY_FILL
                else:
                    self.display[(x*8+i)*DISPLAY_SCALE:(x*8+i+1)*DISPLAY_SCALE,
                        y*DISPLAY_SCALE:(y+1)*DISPLAY_SCALE] = DISPLAY_EMPTY
        elif addr < 0x10000:
            if 0x5000 <= addr < 0xD000 and self.bank_mapping[addr>>12]&0xFFFC not in self.banks:
                raise RuntimeError('[0x{:04X}]mapping error!'.format(addr>>12))
            flash_addr = (self.bank_mapping[addr>>12]<<12)+(addr&0xFFF)
            self.flash[flash_addr] = data
        else:
            raise RuntimeError('address: 0x{:04X} is exceed 0xFFFF'.format(addr))

    def run(self) -> None:
        self.isRunning = True
        while self.isRunning:
            try:
                if self.x6502.run():
                    break
            except Exception as e:
                traceback.print_exc()
                break
            if self.x6502.PC <= 0x0000 or self.x6502.PC >=0x10000:
                print('PC: {:04X}'.format(self.x6502.PC))
                break
        print(', '.join(['0x{:04X}'.format(bank) for bank in self.bank_mapping]))
        print('PC: {:04X}'.format(self.x6502.PC))
        print(f'__stack_ptr: {mcu.memory[0x29]:02X}{mcu.memory[0x28]:02X}')

    def stop(self):
        self.isRunning = False

# - 0x0C40
if __name__ == '__main__':
    mcu = BA4988()
    # mcu.bank_mapping = [
    #     0x0000, 0x0001, 0x0002, 0x0003, 0x0004,
    #     0x0E84, 0x0E85, 0x0E86, 0x0E87,
    #     0x0E80, 0x0E81, 0x0E82, 0x0E83,
    #     0x0E88, 0x0E89, 0x0E8A
    # ]
    # mcu.memory[0x28] = 0xD4
    # mcu.memory[0x29] = 0x17
    # mcu.x6502.PC = 0x549B
    # mcu.bank_mapping = [
    #     0x0000, 0x0001, 0x0002, 0x0003, 0x0004,
    #     0x0E9C, 0x0E9D, 0x0E9E, 0x0E9F,
    #     0x0E80, 0x0E81, 0x0E82, 0x0E83,
    #     0x0E88, 0x0E89, 0x0E8A
    # ]
    # mcu.memory[0x28] = 0xd7
    # mcu.memory[0x29] = 0x17
    # mcu.x6502.PC = 0x72F2
    mcu.x6502.print_layers = 1000
    mcu.run()
    # print('x\ty\tdata\t3B\t3A\t*3A\t\t39\t38\t*38')
    # for x in range(8, 9):
    #     for y in range(64, 66):
    #         for data in [1]:
    #             mcu = BA4988()
    #             mcu.x6502.PC = 0x852A  # SysPutPixel
    #             mcu.x6502.A = x  # x
    #             mcu.flash[0x17AF] = y  # y
    #             mcu.flash[0x17B0] = data  # data
    #             mcu.run()
    #             print('{}\t{}\t{}\t\t{:02X}\t{:02X}\t{:02X}\t\t{:02X}\t{:02X}\t{:02X}'.format(
    #                 x, y, data,
    #                 mcu.memory[0x3B], mcu.memory[0x3A],
    #                 mcu.memory[(mcu.memory[0x3B]<<8)+mcu.memory[0x3A]],
    #                 mcu.memory[0x39], mcu.memory[0x38],
    #                 mcu.memory[(mcu.memory[0x39]<<8)+mcu.memory[0x38]]
    #             ))
    #             print('{:02X}{:02X}\t{:02X}\t{:02X}{:02X}'.format(
    #                 mcu.memory[0x3FC], mcu.memory[0x3FB],
    #                 mcu.memory[0x3FA],
    #                 mcu.memory[0x3F9], mcu.memory[0x3F8]))
